﻿// <copyright file="SourceTreeControl.xaml.cs" company="Soumya Chattopadhyay">
// Copyright (c) 2010 All Right Reserved
// </copyright>
// <disclaimer> This software is intented for educational purposes only and not for 
// commerical or prodution use. The software is provided "as is", without warranty 
// of any kind, express or implied, including but not limited to the warranties of 
// merchantability, fitness for a particular purpose and noninfringement. In no event 
// shall the author(s) or copyright holder(s) be liable for any claim, damages or other 
// liability, whether in an action of contract, tort or otherwise, arising from, out of 
// or in connection with the software or the use or other dealings in the software.
// </disclaimer>

namespace My.CodePlex.TFSExplorer
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.Drawing;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Runtime.InteropServices;
    using System.Security.Cryptography;
    using System.Text;
    using System.Threading;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Interop;
    using System.Windows.Markup;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using Microsoft.TeamFoundation.VersionControl.Client;
    using My.CodePlex.TFSExplorer.Controls;
    using My.CodePlex.TFSExplorer.Resources;
    using My.CodePlex.TFSExplorer.TeamFoundation;

    /// <summary>
    /// Interaction logic for SourceTreeControl.xaml
    /// </summary>
    public partial class SourceTreeControl : UserControl, IExplorerControl
    {
        #region Fields

        /// <summary>
        /// Thread that populates the source tree
        /// </summary>
        private Thread populateSourceTreeThread;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the SourceTreeControl class
        /// </summary>
        public SourceTreeControl()
        {
            InitializeComponent();

            this.ControlName = Resource.SourceTreeControlName;
            this.Header = Resource.SourceTreeTabItemHeader;

            this.SourceFolderView.SelectedItemChanged += this.SourceFolderViewSelectedItemChanged;
            this.SourceFileView.SelectionChanged += this.SourceFileViewSelectionChanged;
            this.SourceHistoryView.ViewDetails += this.SourceHistoryView_ViewDetails;
        }

        #endregion

        #region Events

        /// <summary>
        /// Notify listeners that control has started processing
        /// </summary>
        public event Action<object> OnPopulateStart;

        /// <summary>
        /// Notify listeners that control has finished processing
        /// </summary>
        public event Action<object> OnPopulateEnd;

        /// <summary>
        /// Notify listeners that control has finished processing
        /// </summary>
        public event Action<object> OnPopulateError;

        /// <summary>
        /// The view details event
        /// </summary>
        public event Action<UserControl> ViewDetails;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the workspace path on the local machine
        /// </summary>
        public string WorkSpacePath { get; set; }

        /// <summary>
        /// Gets or sets  the control name
        /// </summary>
        public string ControlName { get; set; }

        /// <summary>
        /// Gets or sets the control tab item header
        /// </summary>
        public string Header { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// Public method called to start the thread to populate the source tree
        /// </summary>
        /// <param name="tfsContext">The TFS context</param>
        public void Populate(TeamFoundationContext tfsContext)
        {
            // Set up the thread to populate the source tree view
            this.populateSourceTreeThread = new Thread(this.PopulateSourceTree);
            this.populateSourceTreeThread.SetApartmentState(ApartmentState.STA);
            this.populateSourceTreeThread.Start(TeamFoundationContext.CurrentProject.ServerItem);

            this.SourceFileView.Dispatcher.BeginInvoke((Action)(() =>
            {
                // TODO: BUG!! Better way?
                TreeViewItem tv = new TreeViewItem();
                WaitCursor wc = new WaitCursor();
                wc.Width = 20;
                wc.Height = 20;
                wc.Start();
                tv.Header = wc;

                this.SourceFolderView.Items.Add(tv);
            }));

            // Notify listeners
            if (this.OnPopulateStart != null)
            {
                this.OnPopulateStart(null);
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Populates the source tree from the version control server
        /// </summary>
        /// <param name="data">Name of the topmost folder in source tree</param>
        private void PopulateSourceTree(object data)
        {
            // Get the top level directories
            ItemSet itemSet = TeamFoundationContext.VersionControlServer.GetItems((string)data, VersionSpec.Latest, RecursionType.OneLevel, DeletedState.NonDeleted, ItemType.Folder);

            if (itemSet.Items.Length == 0)
            {
                if (this.OnPopulateError != null)
                {
                    this.OnPopulateError(null);
                }

                return;
            }

            SourceItem virRoot = new SourceItem();

            ObservableCollection<SourceItem> obsCollection = new ObservableCollection<SourceItem>();

            SourceItem rootItem = new SourceItem();
            rootItem.Name = Path.GetFileName(itemSet.Items[0].ServerItem);
            rootItem.CheckinDate = itemSet.Items[0].CheckinDate.ToLongTimeString() + " " + itemSet.Items[0].CheckinDate.ToShortDateString();
            rootItem.ServerItem = itemSet.Items[0].ServerItem;
            rootItem.ImgUri = new Uri("../Images/ProjectRoot.png", UriKind.Relative);

            virRoot.Items.Add(rootItem);
            this.AddFoldersToTree(rootItem, rootItem.ServerItem);

            // Bind the source tree to the itemsource
            this.SourceFolderView.Dispatcher.BeginInvoke((Action)(() =>
            {
                this.SourceFolderView.Items.Clear();
                this.SourceFolderView.ItemsSource = virRoot.Items;
            }));

            // Notify listeners
            if (this.OnPopulateEnd != null)
            {
                this.OnPopulateEnd(null);
            }
        }

        /// <summary>
        /// Recursively add folders to the tree view
        /// </summary>
        /// <param name="node">The current folder node</param>
        /// <param name="folderName">The current folder name</param>
        private void AddFoldersToTree(SourceItem node, string folderName)
        {
            ItemSet itemSet = TeamFoundationContext.VersionControlServer.GetItems(folderName, VersionSpec.Latest, RecursionType.OneLevel, DeletedState.NonDeleted, ItemType.Folder);
            Item[] itemArray = itemSet.Items;

            foreach (var item in itemArray)
            {
                if (folderName != item.ServerItem)
                {
                    SourceItem newFolderNode = new SourceItem();
                    newFolderNode.IsFolder = true;
                    newFolderNode.Id = item.ItemId;
                    newFolderNode.ChangesetId = item.ChangesetId;
                    newFolderNode.Name = Path.GetFileName(item.ServerItem);
                    newFolderNode.ServerItem = item.ServerItem;

                    newFolderNode.CheckinDate = item.CheckinDate.ToLongTimeString() + " " + itemSet.Items[0].CheckinDate.ToShortDateString();
                    newFolderNode.ImgUri = new Uri("../Images/FolderOpen.png", UriKind.Relative);

                    node.Items.Add(newFolderNode);
                    this.AddFoldersToTree(newFolderNode, item.ServerItem);
                }
            }
        }

        /// <summary>
        /// Check if the file on disk matches the file on the server
        /// </summary>
        /// <param name="item">The Item we are verifying</param>
        /// <returns>True if both match</returns>
        private bool IsLatestFile(Item item)
        {
            // Get mapped file path on the local machine
            string[] splitPath = item.ServerItem.Split('/');
            WorkingFolder wf = TeamFoundationContext.Workspace.GetWorkingFolderForServerItem(item.ServerItem);
            string localFilePath = wf.LocalItem;

            if (!File.Exists(localFilePath))
            {
                return false;
            }

            FileStream stmcheck = File.OpenRead(localFilePath);
            
            if (stmcheck.Length != item.ContentLength)
            {
                return false;
            }

            byte[] bytes = item.HashValue;
            byte[] hash = new MD5CryptoServiceProvider().ComputeHash(stmcheck);

            if (bytes.Length != hash.Length)
            {
                return false;
            }

            for (int i = 0; i < bytes.Length; i++)
            {
                if (bytes[i] != hash[i])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// When the source folder selection changes we'll query it's history
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void SourceFolderViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
        {
            SourceItem sourceItem = this.SourceFolderView.SelectedItem as SourceItem;
            this.DisplayFolderContents(sourceItem);
            this.DisplayHistory(sourceItem);

            this.HistoryViewLabel.Content = "History for folder " + sourceItem.Name;
            WorkingFolder wf = TeamFoundationContext.Workspace.GetWorkingFolderForServerItem(sourceItem.ServerItem);
            this.FolderPathLabel.Content = wf.LocalItem;
        }

        /// <summary>
        /// Display history of the folder
        /// </summary>
        /// <param name="sourceItem">The current selected folder</param>
        private void DisplayHistory(SourceItem sourceItem)
        {
            this.SourceHistoryView.Populate(sourceItem);
        }

        /// <summary>
        /// Display the (file) contents of the folder
        /// </summary>
        /// <param name="sourceItem">The current selected folder</param>
        private void DisplayFolderContents(SourceItem sourceItem)
        {
            this.SourceFileView.Dispatcher.BeginInvoke((Action)(() =>
            {
                ItemSet itemSet = TeamFoundationContext.VersionControlServer.GetItems(sourceItem.ServerItem, VersionSpec.Latest, RecursionType.OneLevel, DeletedState.NonDeleted, ItemType.File);
                Item[] itemArray = itemSet.Items;

                ObservableCollection<SourceItem> obsCollection = new ObservableCollection<SourceItem>();

                foreach (var item in itemArray)
                {
                    SourceItem fileItem = new SourceItem();
                    fileItem.ServerItem = item.ServerItem;
                    fileItem.Name = Path.GetFileName(item.ServerItem);

                    fileItem.ImgSource = FileIconRepository.GetFileIconImage(Path.GetExtension(fileItem.Name)).ToImageSource();
                    fileItem.Id = item.ItemId;
                    fileItem.ChangesetId = item.ChangesetId;

                    fileItem.CheckinDate = item.CheckinDate.ToShortDateString() + " " + item.CheckinDate.ToLongTimeString();

                    ItemSpec[] itemSpecs = new ItemSpec[1];
                    itemSpecs[0] = new ItemSpec(item.ServerItem, RecursionType.Full);
                    PendingSet[] pendingSet = TeamFoundationContext.VersionControlServer.QueryPendingSets(itemSpecs, null, null);

                    string[] ite = new string[] { item.ServerItem };

                    PendingSet[] sets = TeamFoundationContext.VersionControlServer.QueryPendingSets(ite, RecursionType.None, null, null);

                    if (sets.Length == 0)
                    {
                        fileItem.IsCheckedout = false;
                    }
                    else
                    {
                        StringBuilder sb = new StringBuilder();
                        foreach (PendingSet set in sets)
                        {
                            sb.Append(set.OwnerName.ToLower() + " [" + set.Computer + "]; ");

                            if (string.Compare(set.OwnerName, TeamFoundationContext.CurrentDisplayUser, true) == 0)
                            {
                                fileItem.IsCheckedout = true;
                            }
                        }

                        fileItem.Client = sb.ToString();
                    }

                    if (this.IsLatestFile(item))
                    {
                        fileItem.IsLatestVersion = string.Empty;
                    }
                    else
                    {
                        fileItem.IsLatestVersion = "No";
                    }

                    obsCollection.Add(fileItem);
                }

                this.SourceFileView.ItemsSource = obsCollection;
            }));
        }

        /// <summary>
        /// When the source file selection changes we'll query it's history
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void SourceFileViewSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (this.SourceFileView.SelectedItem != null)
            {
                SourceItem sourceItem = this.SourceFileView.SelectedItem as SourceItem;
                this.DisplayHistory(sourceItem);
                this.HistoryViewLabel.Content = "History for file " + sourceItem.Name;
            }
        }

        /// <summary>
        /// Raise the view details event
        /// </summary>
        /// <param name="obj">The user control to display</param>
        private void SourceHistoryView_ViewDetails(UserControl obj)
        {
            if (this.ViewDetails != null)
            {
                this.ViewDetails(obj);
            }
        }

        /// <summary>
        /// Diplay the relevant context menu
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void SourceFileView_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
        {
            if (this.SourceFileView.SelectedItem == null)
            {
                this.SourceFileViewContextMenu.Visibility = Visibility.Collapsed;
            }
            else
            {
                this.SourceFileViewContextMenu.Visibility = Visibility.Visible;

                SourceItem sourceItem = this.SourceFileView.SelectedItem as SourceItem;
                if (sourceItem.IsCheckedout)
                {
                    this.CheckinMenuItem.Visibility = Visibility.Visible;
                    this.CheckoutMenuItem.Visibility = Visibility.Collapsed;
                    this.UndoCheckoutMenuItem.Visibility = Visibility.Visible;
                }
                else
                {
                    this.CheckinMenuItem.Visibility = Visibility.Collapsed;
                    this.CheckoutMenuItem.Visibility = Visibility.Visible;
                    this.UndoCheckoutMenuItem.Visibility = Visibility.Collapsed;
                }
            }
        }

        /// <summary>
        /// Check out files
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void CheckoutMenuItemClick(object sender, RoutedEventArgs e)
        {
            List<string> pending = new List<string>();

            // Get all the checkboxes and check them in
            foreach (SourceItem pend in this.SourceFileView.SelectedItems)
            {
                pending.Add(pend.ServerItem);
            }

            try
            {
                TeamFoundationContext.Workspace.PendEdit(pending.ToArray());

                // Get all the checkboxes and mark them as checked out
                foreach (SourceItem pend in this.SourceFileView.SelectedItems)
                {
                    pend.IsCheckedout = true;
                }
            }
            catch (VersionControlException ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message, Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Check in all the selected files
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void CheckinMenuItemClick(object sender, RoutedEventArgs e)
        {
            List<string> pending = new List<string>();

            // Get all the checkboxes and check them in
            foreach (SourceItem pend in this.SourceFileView.SelectedItems)
            {
                pending.Add(pend.ServerItem);
            }

            PendingChange[] allPendingChange = TeamFoundationContext.Workspace.GetPendingChanges();

            PendingChange[] pendingChange = TeamFoundationContext.Workspace.GetPendingChanges(pending.ToArray());
            CheckinEvaluationResult result = TeamFoundationContext.Workspace.EvaluateCheckin(CheckinEvaluationOptions.All, allPendingChange, pendingChange, string.Empty, null, null);
            if (result.Conflicts.Length != 0)
            {
                StringBuilder sb = new StringBuilder();

                foreach (CheckinConflict ck in result.Conflicts)
                {
                    sb.Append(ck.ServerItem + " : " + ck.Message + " : " + ck.Resolvable + Environment.NewLine);
                }

                System.Windows.Forms.MessageBox.Show(sb.ToString());
            }

            try
            {
                int changesetNumber = TeamFoundationContext.Workspace.CheckIn(pendingChange, string.Empty);
            }
            catch (CheckinException ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message, Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Undo checkout files
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void UndoCheckoutMenuItemClick(object sender, RoutedEventArgs e)
        {
            List<string> pending = new List<string>();

            // Get all the checkboxes and check them in
            foreach (SourceItem pend in this.SourceFileView.SelectedItems)
            {
                pending.Add(pend.ServerItem);
            }

            try
            {
                PendingChange[] pendingChange = TeamFoundationContext.Workspace.GetPendingChanges(pending.ToArray());

                if (pendingChange.Length != 0)
                {
                    TeamFoundationContext.Workspace.Undo(pendingChange);

                    foreach (SourceItem pend in this.SourceFileView.SelectedItems)
                    {
                        pend.IsCheckedout = false;
                    }
                }
            }
            catch (VersionControlException ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message, Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Get the latest version of the file
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void GetLatestVersionClick(object sender, RoutedEventArgs e)
        {
            List<string> pending = new List<string>();

            // Get all the checkboxes and check them in
            foreach (SourceItem pend in this.SourceFileView.SelectedItems)
            {
                pending.Add(pend.ServerItem);
            }

            try
            {
                GetStatus getStatus = TeamFoundationContext.Workspace.Get(pending.ToArray(), VersionSpec.Latest, RecursionType.None, GetOptions.None);
                if (getStatus.NumFailures != 0)
                {
                    Failure[] failures = getStatus.GetFailures();
                    StringBuilder sb = new StringBuilder();
                    foreach (Failure failure in failures)
                    {
                        sb.Append(failure.ServerItem + ":" + failure.Message + Environment.NewLine);
                    }

                    System.Windows.Forms.MessageBox.Show(sb.ToString(), Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
                }
            }
            catch (VersionControlException ex)
            {
                System.Windows.Forms.MessageBox.Show(ex.Message, Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Get the latest version of the folder
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void GetLatestFolderVersionClick(object sender, RoutedEventArgs e)
        {
            if (this.SourceFolderView.SelectedItem != null)
            {
                TreeViewItemWithIcons treeViewItemWithIcons = this.SourceFolderView.SelectedItem as TreeViewItemWithIcons;
                SourceItem sourceItem = treeViewItemWithIcons.Tag as SourceItem;

                string[] items = new string[] { sourceItem.ServerItem };

                GetStatus getStatus = TeamFoundationContext.Workspace.Get(items, VersionSpec.Latest, RecursionType.Full, GetOptions.None);
                if (getStatus.NumFailures != 0)
                {
                    Failure[] failures = getStatus.GetFailures();
                    StringBuilder sb = new StringBuilder();
                    foreach (Failure failure in failures)
                    {
                        sb.Append(failure.ServerItem + ":" + failure.Message + Environment.NewLine);
                    }

                    System.Windows.Forms.MessageBox.Show(sb.ToString(), Resource.ProductNameLong, System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
                }
            }
        }

        /// <summary>
        /// Check out a folder
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void CheckoutFolderMenuItemClick(object sender, RoutedEventArgs e)
        {
            if (this.SourceFolderView.SelectedItem != null)
            {
                TreeViewItemWithIcons treeViewItemWithIcons = this.SourceFolderView.SelectedItem as TreeViewItemWithIcons;
                SourceItem sourceItem = treeViewItemWithIcons.Tag as SourceItem;

                TeamFoundationContext.Workspace.PendEdit(sourceItem.ServerItem, RecursionType.Full);

                this.DisplayFolderContents(sourceItem);
                this.DisplayHistory(sourceItem);
            }
        }

        /// <summary>
        /// Open a folder location in windows explorer
        /// </summary>
        /// <param name="sender">The sender object</param>
        /// <param name="e">The event arguments</param>
        private void OpenExplorerMenuItemClick(object sender, RoutedEventArgs e)
        {
            if (this.SourceFolderView.SelectedItem != null)
            {
                TreeViewItemWithIcons treeViewItemWithIcons = this.SourceFolderView.SelectedItem as TreeViewItemWithIcons;
                SourceItem sourceItem = treeViewItemWithIcons.Tag as SourceItem;

                WorkingFolder wf = TeamFoundationContext.Workspace.GetWorkingFolderForServerItem(sourceItem.ServerItem);
                Process.Start(wf.LocalItem);
            }
        }

        #endregion
    }
}